package com.bodgittandscarper.brokerhmi.control;

import java.lang.reflect.InvocationTargetException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import suncertify.db.RecordNotFoundException;

import com.bodgittandscarper.broker.client.BrokerClient;
import com.bodgittandscarper.broker.server.Server;
import com.bodgittandscarper.broker.types.DatabaseSchema;
import com.bodgittandscarper.broker.types.ModeFlag;
import com.bodgittandscarper.broker.types.Record;
import com.bodgittandscarper.broker.utils.PropertyHandler;
import com.bodgittandscarper.brokerhmi.model.HMIModel;

/**
 * GUI control class responsible for communication with the Server part of the Application. 
 * This can either be in a networked mode(client) or a non-networked mode(alone) In either cases the same boundary class 
 * (<code>BrokerHMI</code>) is used.  
 * 
 * @author Morten Rummelhoff
 *
 */
public class BASControl {

	Logger log = Logger.getLogger(getClass().getName());

	private BrokerClient brokerClient; 
	
	private DatabaseSchema databaseSchema;
	
	private final static String SERVER_HOST_IP_PROPERTY_NAME = "server_host";
	
	final private ModeFlag mode;
	final private HMIModel hmiModel;
	
	private String serverAddress;
	
	
	/**
	 * Constructor specifying mode and a server reference. In Alone mode, the Server reference is used to get a reference to 
	 * the <code>BrokerClient</code>.
	 * 
	 * <p>
	 * In client mode, the BrokerClient are looked up in the RMI registry. Note that the Server application must be running and 
	 * a BrokerClient service must be associated to the RMI registry. Otherwise a <code>NotBoundException</code> with be thrown.  
	 * 
	 * @param mode Specifies mode of application. Mode can either be Client or Alone to determine if the GUI is communicating 
	 * in a networked or non-networked mode.
	 * @param server Specifies the Server reference. This reference is only used in Alone mode to get a reference to the 
	 * <code>BrokerClient</code>
	 * @throws RemoteException
	 * @throws NotBoundException
	 */
	public BASControl(ModeFlag mode, HMIModel hmiModel, Server server) throws RemoteException, NotBoundException {
		if (log.isLoggable(Level.INFO))
			log.log(Level.INFO, "Creating BrokerClient");
		
		this.mode = mode;
		this.hmiModel = hmiModel;
		
		//Server already initialized in Start when running in ALONE mode
		if (this.mode == ModeFlag.ALONE) {
			brokerClient = server.getBrokerClient();
		}
		else {
			 
			final String storedValue = PropertyHandler.getInstance().getProperty(SERVER_HOST_IP_PROPERTY_NAME);
			
			//Start popup dialog and wait for user input
			try {
				SwingUtilities.invokeAndWait(new Runnable() {
					
					@Override
					public void run() {
						
						 serverAddress = (String)JOptionPane.showInputDialog(
			                    new JFrame(),
			                    "Specify Server Address:",
			                    "Brogitt & Scarpers - Server Address",
			                    JOptionPane.DEFAULT_OPTION,
			                    null,
			                    null,
			                    storedValue);
					
					
						PropertyHandler.getInstance().setProperty(SERVER_HOST_IP_PROPERTY_NAME, serverAddress);
						PropertyHandler.getInstance().storeProperty();
					}
					
				});
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				//
				System.exit(-1);
			}
			//store property to propertyfile.
			PropertyHandler.getInstance().storeProperty();
			
			Registry registry = LocateRegistry.getRegistry(serverAddress);
			brokerClient = (BrokerClient) registry.lookup(Server.RMI_SERVICE_NAME);
		
		}
		
		//the databaseschema does not change during runtime, therefore a local reference can stored here.
		databaseSchema = brokerClient.getDatabaseSchema();
		
	}
	
	/**
	 * Gets mode of the application. 
	 * 
	 * @return the mode. 
	 */
	public ModeFlag getMode() {
		return mode;
	}
	
	
	/**
	 * Gets the databaseschema for the database. 
	 * 
	 * @return a local reference of the databaseschema. The reference is initialized in the costructor.
	 */
	public DatabaseSchema getDatabaseScheme() {
		return databaseSchema;
	}
	
	/**
	 * Finds {@link Record}s that exact matches the given criteria in the {@link Record} list. 
	 * Search criteria is specified by the name and/or location parameter. If the name criteria is empty,
	 * only location criteria is used to find exact matches. And if location criteria is empty, 
	 * only the name criteria is matched.
	 * <br>
	 * If both name and location criteria is empty no {@link Record} will match.
	 * <p>
	 * An exact {@link Record} match is specified by the <code>equals</code> method in the {@link String} class.
	 * 
	 * @param name Specifies the name criteria. If empty, all {@link Record}'s will match if 
	 * location criteria is not empty. 
	 * @param location Specifies the location criteria. If empty, all {@link Record}'s will match if 
	 * name criteria is not empty.
	 * @return an array of {@link Record}'s that exact matches the specified criteria. 
	 */
	public Record[] findExactRecords(String name, String location) throws RecordNotFoundException, RemoteException {
		Record[] records = brokerClient.findRecords(new String[] {name, location, null, null, null, null});
		
		//GUI stated requirement: Only exact records shall be presented. 
		
		List<Record> exactMatchRecordList = new ArrayList<Record>();

		for (Record record : records ) {

			String recordFieldName = record.getRecordFields()[0].trim();
			String recordFieldLocation = record.getRecordFields()[1].trim();
			String criteriaFieldName = ((name != null) ? name.trim() : "");
			String criteriaFieldLocation = ((location != null) ? location.trim() : "");

			//first look at name criteria
			if (criteriaFieldName.length() > 0 && 
					criteriaFieldName.equals(recordFieldName) == false)  {
				continue;
			}
			//then look at location criteria
			if (criteriaFieldLocation.length() > 0 && 
					(criteriaFieldLocation.equals(recordFieldLocation) == false)) {
				continue;
			}

			//check if both are empty
			if (criteriaFieldName.length() == 0 && criteriaFieldLocation.length() == 0) {
				continue;
			}
			
			exactMatchRecordList.add(record);
			
		}

		return (Record[])exactMatchRecordList.toArray(new Record[exactMatchRecordList.size()]);
	}
	
	/**
	 * Gets all valid marked {@link Record} in the Database.
	 * 
	 * @return All {@link Record}'s in the database. If no {@link Record}'s exist in the 
	 * database, null is returned.
	 * @throws RemoteException
	 */
	public Record[] getAllRecords() throws RemoteException {
		try {
			return brokerClient.findRecords(new String[] {""} );
		} catch (RecordNotFoundException e) {
			//ignore. If no record exist, just return null
		}
		return null;
	}
	
	/**
	 * Calls the corresponding {@link BrokerClient} <tt>updateRecord<tt> method.
	 * 
	 * @see BrokerClient#updateRecord(int, String[])
	 */
	public void updateRecord(int recNo, String[] recordData) throws RecordNotFoundException, RemoteException {
		brokerClient.updateRecord(recNo, recordData);
	}
	
	/**
	 * Calls the corresponding {@link BrokerClient} <tt>lockRecord<tt> method.
	 * 
	 * @see BrokerClient#lockRecord(int)
	 */
	public void lockRecord(int recNo) throws RecordNotFoundException, RemoteException {
		brokerClient.lockRecord(recNo);
	}

	/**
	 * Calls the corresponding {@link BrokerClient} <tt>unlockRecord<tt> method.
	 * 
	 * @see BrokerClient#unlockRecord(int)
	 */
	public void unlockRecord(int recNo) throws RecordNotFoundException, RemoteException {
		brokerClient.unlockRecord(recNo);
	}

	
}